﻿using System;
using System.Collections.Generic;
using System.IO;


namespace AdaptiveAgents.Distributions 
{
    /// <summary>
    /// Inherits from IDistribution
    /// Represents Blocks distribution
    /// This reprisents a distributuin assembeld from "Bloks" with:
    /// 1. Start and end values (for the location of the block. Must be between 0 and 1!)
    /// 2. Height
    /// from this we get the value of the block.
    /// the sum of all the values will be 1.
    /// the distribution start 
    /// </summary>
    public class Blocks : IDistribution
    {
        //data members
        private double _mean; //the mean of the distribution
        private Block[] _blockArray; //the "Bloks" that the distribution is assembeld from

        /// <summary>
        /// Represents one single Block
        /// </summary>
        private class Block
        {
            //data members
            double _start; //start value
            double _end; //end value
            double _height; 
            double _value; //space inside the Block

            /// <summary>
            /// Set and get the start value of the Block 
            /// </summary>
            public double Start
            {
                get { return _start; }
                set { _start = value; }
            }

            /// <summary>
            /// Set and get the end value of the Block
            /// </summary>
            public double End
            {
                get { return _end; }
                set { _end = value; }
            }

            /// <summary>
            /// Set and get the height of the block
            /// On setting the height - the value of the Block is changing accordingly
            /// </summary>
            public double Height
            {
                get { return _height; }
                set {
                    _height = value;
                    _value = (_end - _start) * value;
                }
            }

            /// <summary>
            /// Set and get the value (space) of the block
            /// On setting the value - the height of the Block is changing accordingly
            /// </summary>
            public double Value
            {
                get { return _value; }
                set { 
                    _value = value;
                    _height = value / (_end - _start);
                }
            }
        }

        #region constructors --------------------

        /// <summary>
        /// Craeate a Blocks distribution with random values and a random mean.
        /// </summary>
        public Blocks()
        {
            //create the Blocks
            generateRandomValues();

            //calculate the mean
            Double sum = 0;
            for (int i = 0; i < _blockArray.Length; i++)
            {
                sum += ((_blockArray[i].End + _blockArray[i].Start) * _blockArray[i].Value / 2);
            }
            _mean= sum;
        }

        public Blocks(double wantedMean)
        {
            generateRandomValues(wantedMean);
            Double sum = 0;
            for (int i = 0; i < _blockArray.Length; i++)
            {
                sum += ((_blockArray[i].End + _blockArray[i].Start) * _blockArray[i].Value / 2);
            }
            _mean = sum;
        }

        public Blocks(List<Point> points)
        {
            int inputNumSteps = points.Count-1;

            Double startValue, height, sum = 0;

            _blockArray = new Block[inputNumSteps];
            for (int i = 0; i < inputNumSteps; i++)
                _blockArray[i] = new Block();
   
            startValue = points[0].X;
            height = points[0].Y;
            for (int i = 0; i < inputNumSteps; i++)
            {
                _blockArray[i].Start = startValue;
                _blockArray[i].End = points[i + 1].X;
                //_blockArray[i].Value = value;
                //sum += value;
                _blockArray[i].Height = height;
                sum += _blockArray[i].Value;
                startValue = points[i + 1].X;
                height = points[i + 1].Y;
            }

            for (int i = 0; i < inputNumSteps; i++)
            {
                if (_blockArray[i].Start > _blockArray[i].End)
                {
                    System.Console.Error.WriteLine("problem in rect " + i + " - end is greater then start");
                    System.Diagnostics.Debug.Assert(false);
                }
            }

            //1st normaliztion:
            sum = 0;
            for (int i = 0; i < _blockArray.Length; i++)
            {
                sum += _blockArray[i].Value;
            }
            if (sum !=1 )
                for (int i = 0; i < _blockArray.Length; i++)
                {
                    _blockArray[i].Value = _blockArray[i].Value / sum;
                }

            //2nd normaliztion:
            sum = 0;
            if (_blockArray[_blockArray.Length-1].End > 1)
                for (int i = 0; i < _blockArray.Length; ++i)
                {
                    _blockArray[i].Start = _blockArray[i].Start / startValue;
                    _blockArray[i].End = _blockArray[i].End / startValue;
                    _blockArray[i].Value = _blockArray[i].Value; //to fix the height
                    sum += ((_blockArray[i].End + _blockArray[i].Start) * _blockArray[i].Value / 2);
                }
            else
                for (int i = 0; i < _blockArray.Length; ++i)
                {
                    sum += ((_blockArray[i].End + _blockArray[i].Start) * _blockArray[i].Value / 2);
                }
            _mean = sum;
        }

        public Blocks(String fileName)
        {
            try
            {
                StreamReader reader = new StreamReader(fileName);
                String line = reader.ReadLine();
                line.Trim();
                if (line.Equals(Distributions.DistributionType.Blocks.ToString().ToLower()))
                {
                    line = reader.ReadLine();
                    int inputNumSteps = int.Parse(line);
                    Double startValue, value, sum = 0;

                    _blockArray = new Block[inputNumSteps];

                    for (int i = 0; i < inputNumSteps; i++)
                        _blockArray[i] = new Block();
                    string [] inputDataArray=new string[2];
                    char[] cArr = new char[1];
                    cArr[0] = ',';
                    line = reader.ReadLine();
                    inputDataArray = line.Split(cArr);
                    startValue = double.Parse(inputDataArray[0]);
                    value = double.Parse(inputDataArray[1]);
                    for (int i = 0; i < inputNumSteps; i++)
                    {
                        line = reader.ReadLine();
                        inputDataArray = line.Split(cArr);
                        _blockArray[i].Start = startValue;
                        _blockArray[i].End=double.Parse(inputDataArray[0]);
                        _blockArray[i].Value=value;
                        sum += value;
                        startValue = double.Parse(inputDataArray[0]);
                        value = double.Parse(inputDataArray[1]);
                    }
                    for (int i = 0; i < inputNumSteps; i++)
                    {
                        if (_blockArray[i].Start > _blockArray[i].End)
                        {
                            System.Console.Error.WriteLine("problem in rect " + i + " - end is greater then start");
                            System.Diagnostics.Debug.Assert(false);
                        }
                    }
                    if (sum != 1)
                    {
                        System.Console.Error.WriteLine("Distribution dosen't add up to 1");
                        System.Diagnostics.Debug.Assert(false);

                    }

                    //normaliztion:
                    sum = 0;
                    for (int i = 0; i < _blockArray.Length; ++i)
                    {
                        _blockArray[i].Start = _blockArray[i].Start / startValue;
                        _blockArray[i].End = _blockArray[i].End / startValue;
                        _blockArray[i].Value = _blockArray[i].Value; //to fix the height
                        sum += ((_blockArray[i].End + _blockArray[i].Start) * _blockArray[i].Value / 2);
                    }
                    _mean = sum;
                }
                else
                {
                    System.Console.Error.WriteLine("trying to read unknown distribution type");
                    System.Diagnostics.Debug.Assert(false);
                }

                reader.Close();  // Close to unlock.
            }
            catch (IOException e)
            {
                System.Console.Error.WriteLine("Unable to read from file");
                System.Diagnostics.Debug.Assert(false);
            }
        }

        #endregion constructors --------------------


        /// <summary>
        /// Gets the mean of the distribution
        /// </summary>
        public double Mean { get { return _mean; } }

        /// <summary>
        /// Gets the variance of the distribution
        /// </summary>
        /// //TODO - find variance
        public double Variance { get { return 0; } }

        /// <summary>
        /// Create a random set of blocks.
        /// Put the set of blocks created in the data member "_blockArray"
        /// </summary>
        public void generateRandomValues()
        {
            generateRandomValues(Generator.getNextDouble());
        }
        
        /// <summary>
        /// Generates random distribution values according to the wanted mean as given in the parameter. 
        /// </summary>
        /// <param name="wantedMean">The mean you want the distribution to have</param>
        public void generateRandomValues(double wantedMean)
        {
            //data members
            Double sum = 0;
            //Pick random number of creation round (min:1. max:6)
            //On each creation round we create 2 Blocks:
            //one on the one side of the mean and another on the other side of the mean
            int rndNumSteps = (int)(Generator.getNextDouble() * 5) + 1; 

            List<Block> tempBlockArray = new List<Block>(rndNumSteps);
            Block tempBlock1 = null;
            Block tempBlock2 = null;
            double currMean = 0;

            if (Generator.getNextDouble() > 0.5) //create a 50% chance for creating an odd number of primary blocks
            {
                //remember that the mean of this block must be the mean of the distribution!
                double firstBase = Generator.getNextDouble(); //set the base length
                //fix boundry problems
                if (wantedMean + firstBase / 2 > 1)
                    firstBase = (1 - wantedMean) * 2;
                if (wantedMean - firstBase / 2 < 0)
                    firstBase = wantedMean * 2;
                //create a new block and set its start and end values
                tempBlock1 = new Block();
                tempBlock1.Start = wantedMean - firstBase / 2;
                tempBlock1.End = wantedMean + firstBase / 2;
                tempBlock1.Value = Generator.getNextDouble(); //pick a random value for the value of the block
                sum += tempBlock1.Value; //add the value for the normalization part later
                tempBlockArray.Add(tempBlock1); //add the Block to the array
                currMean = wantedMean;
            }

            //creation rounds (explained before)
            for (int i = 0; i < rndNumSteps; i++)
            {
                //create one Block on the right side of the mean
                double firstMean = Generator.getNextDouble() * wantedMean;
                double firstBase = Generator.getNextDouble();
                if (firstMean + firstBase / 2 > 1)
                    firstBase = (1 - firstMean) * 2;
                if (firstMean - firstBase / 2 < 0)
                    firstBase = firstMean * 2;
                tempBlock1 = new Block();
                tempBlock1.Start = firstMean - firstBase / 2;
                tempBlock1.End = firstMean + firstBase / 2;
                tempBlock1.Value = Generator.getNextDouble();
                tempBlockArray.Add(tempBlock1);


                //create one Block on the left side of the mean
                double secondMean = Generator.getNextDouble() * (1 - wantedMean) + wantedMean;
                double secondBase = Generator.getNextDouble();
                if (secondMean + secondBase / 2 > 1)
                    secondBase = (1 - secondMean) * 2;
                if (secondMean - secondBase / 2 < 0)
                    secondBase = secondMean * 2;
                tempBlock2 = new Block();
                tempBlock2.Start = secondMean - secondBase / 2;
                tempBlock2.End = secondMean + secondBase / 2;
                //the second Block value is calculated so the mean of tempBlock1 and tempBlock2 
                //will be the wanted mean of the distribution
                tempBlock2.Value = tempBlock1.Value * Math.Abs(wantedMean - firstMean) / Math.Abs(secondMean - wantedMean);
                tempBlockArray.Add(tempBlock2);

                //sum the values of the Blocks for the normalization part later
                sum += tempBlock1.Value;
                sum += tempBlock2.Value;

            }

            //in the mothod we used to create the Blocks - there can be 2 "HOFEFIM" Blocks.
            //this code deals with creating new blocks so if is an area with 2 Bloks -
            //we will create one block that the value of the Block will be the sum of values of the 2 Blocks.
            //1. create an array with all the points of the Blocks.
            List<double> points = new List<double>();
            for (int i = 0; i < tempBlockArray.Count; i++)
            {
                points.Add(tempBlockArray[i].Start);
                points.Add(tempBlockArray[i].End);
            }
            //2. sort the array
            points.Sort();

            //3. create a Blocks array for the new Blocks
            _blockArray = new Block[points.Count - 1];

            double mid;
            for (int i = 0; i < points.Count - 1; i++)
            {
                //4. create a new Block according to the Points
                _blockArray[i] = new Block();
                _blockArray[i].Start = points[i];
                _blockArray[i].End = points[i + 1];
                //5. find the middle of the new Block
                mid = (points[i] + points[i + 1]) / 2;
                //6. run over all the old Blocks and sum the height of all the Blocks that the middle is inside them.
                for (int j = 0; j < tempBlockArray.Count; j++)
                    if (mid > tempBlockArray[j].Start && mid < tempBlockArray[j].End)
                        _blockArray[i].Height += tempBlockArray[j].Height;
            }

            //normalization step:
            sum = 0;
            //1. sum the value of all blocks
            for (int i = 0; i < _blockArray.Length; i++)
            {
                sum += _blockArray[i].Value;
            }

            //2. divide each value by the sum
            for (int i = 0; i < _blockArray.Length; i++)
            {
                _blockArray[i].Value = _blockArray[i].Value / sum;
            }
        }

        /// <summary>
        /// Gets a description of the distribution.
        /// </summary>
        /// <returns>String representation of the distribution.
        /// The right upper point of each Block</returns>
        public String ToString()
        {
            String desc = "Blocks"; //new String("Blocks:".ToCharArray());
            double currStart = 0;
            for (int i = 0; i < _blockArray.Length; ++i) 
            {
                if (_blockArray[i].Start != currStart)
                {
                    desc += "(" + currStart + ",0),";
                    currStart = _blockArray[i].End;
                }
                desc += "(" + _blockArray[i].Start + "," + _blockArray[i].Height + "),";
            }
            desc += "(" + _blockArray[_blockArray.Length-1].End + ",0)";
            return desc;
        }

        /// <summary>
        /// Gets f(x) (probability density function)
        /// </summary>
        /// <param name="x">Random variable</param>
        /// <returns>f(x) according to the distribution function</returns>
        public double valueOf(double x)
        {
            double sum = 0;
            int i = 0;
            if (x > _blockArray[_blockArray.Length-1].End)
                return 0;
            while (_blockArray[i].End < x)
            {
                ++i;
            }
            return _blockArray[i].Height;
        }

        /// <summary>
        /// Gets F(x) (probability cumulative density function)
        /// </summary>
        /// <param name="x">Random variable</param>
        /// <returns>F(x) according to the distribution function</returns>
        public double cumulativeValueOf(double x)
        {
            if (x > _blockArray[_blockArray.Length].End)
                return 1;
            else
            {
                int i = 0;
                double sum = 0;
                //find the last Block that ends before x
                while (_blockArray[i].End < x)
                {
                    sum += _blockArray[i].Value;
                    ++i;
                }
                //calc (if needed) witch part of the Block's value to return
                if (x < _blockArray[i].Start)
                    return sum;
                else
                    return sum + (x - _blockArray[i].Start) * _blockArray[i].Height;                
            }
        }

        /// <summary>
        /// Gets a sample of the distribution values
        /// </summary>
        /// <returns>Array of points that were sampled from the distribution function</returns>
        public Point[] sample()
        {
            int size=100;
            Point[] pointArray = new Point[size];
            double x;
            for(int i=0; i<size;++i)
                {x=Generator.getNextDouble();
                pointArray[i] = new Point(x, valueOf(x));
            }
            return pointArray;
        }

        /// <summary>
        /// Gets the distribution type
        /// </summary>
        public DistributionType Type { get { return DistributionType.Blocks; } }

        /// <summary>
        /// Generates a random value
        /// </summary>
        /// <returns>Random value according to distribution type</returns>
        public double generateValue()
        {
            double randomNumber = Generator.getNextDouble();
            double sum = 0;
            int i = 0;
            while (sum + _blockArray[i].Value < randomNumber)
            {
                sum += _blockArray[i].Value;
                ++i;
            }
            double number = (_blockArray[i].End - _blockArray[i].Start) * (randomNumber - sum) / _blockArray[i].Value + _blockArray[i].Start;
            return number;
        }

    }
}
